module yaml.yamld;

import yaml.libyaml;

import 
	Int	= tango.text.convert.Integer ,
	tango.stdc.stringz;

enum{
	Seq ,
	Map ,
	Str ,
	Float ,
	Bool ,
	Bin ,
}

final class Node {
	enum {
		Scalar ,
		Sequence ,
		Mapping ,
	}
	
	int			type ;
	bool			flowStyle ;
	char[]		anchor ;
	Element[]		lists ;
	Element[Element]	hash ;
	
	char[] value(){
		assert( type is Scalar );
		return anchor ;
	}
	
	Element opIndex (size_t idx){
		assert( type is Sequence || type is Mapping);
		return lists[idx];
	}
	
	Element addNode(Element e){ //opOpAssign
		assert( type is Sequence || type is Mapping );
		this.lists	~= e ;
		return this ;
	}
	
	Element opIndex (Element idx) {
		assert( type is Mapping);
		assert( idx !is null);
		assert( idx.type is Scalar );
		return hash[ idx ];
	}
	
	Element addNode(Element el, Element idx) {
		assert( type is Mapping);
		assert( type is Mapping);
		assert( idx !is null);
		assert( idx.type is Scalar );
		if ((idx in hash) is null)
			lists		~= idx;
		hash[idx]		= el;
		return el;
	}
	
	size_t length () {
		assert( type is Mapping || type is Sequence );
		return lists.length;
	}
	
	static Element Init(yaml_event_t* event){
		Element	self	= new Node ;
		Stdout.formatln("{}	= `{}`",  event.type, (cast(ubyte*)event)[0.. (*event).sizeof ] );
		
		if( event is null ){
			
		}else{
			switch(event.type){
				case yaml_event_type_t.YAML_SCALAR_EVENT:
					self.anchor = fromStringz( cast(char*) event.data.scalar.anchor );
					self.type	= Scalar ;
					break;
				case yaml_event_type_t.YAML_SEQUENCE_START_EVENT:
					self.anchor = fromStringz( cast(char*) event.data.sequence_start.anchor );
					self.type	= Sequence ;
					break;
				case yaml_event_type_t.YAML_MAPPING_START_EVENT:
					self.anchor = fromStringz( cast(char*) event.data.mapping_start.anchor );
					self.type	= Mapping ;
					break;
				default:
					assert(false);
			}
		}
		return self ;
	}

	hash_t toHash () {
		if( type is Scalar ){
			return typeid( typeof(anchor) ). getHash(&anchor);
		}
		return cast(hash_t) cast(void*) this ;
	}
	
	/+@safe const+/
	int opCmp (Node o) {
		if (this is o)
			return 0;
		if ( o.type is Scalar )
			return anchor > o.anchor;
		throw new Exception("not implemented");
	}
}

alias Node	Element ;

struct Doc {
	private{
		uint level ;
	}
	public{
		uint		indent	= 3 ;
		Element	root ;
	}
	
	static Doc* Init(Element mapping ) {
		assert( mapping.type == Node.Mapping );
		Doc* doc	= new Doc;
		doc.root	= mapping ;
		return doc;
	}
}

final class Parser {
	private {
		yaml_parser_t	parser ;
		Element[char[]]	aliases ;
	}
	
	
	this (){	
		yaml_parser_initialize(&parser);
	}
	
	~this () {
		yaml_parser_delete(&parser);
	}
	
	uint errorCode ()
	{
		return cast(uint) parser.error;
	}
	
	char[] errorMsg () {
		switch (errorCode){
			case yaml_error_type_t.YAML_NO_ERROR:
				return "No error is produced.";
			case yaml_error_type_t.YAML_MEMORY_ERROR:
				return "Cannot allocate or reallocate a block of memory.";
			case yaml_error_type_t.YAML_READER_ERROR:
				return "Cannot read or decode the input stream.";
			case yaml_error_type_t.YAML_SCANNER_ERROR:
				return "Cannot scan the input stream.";
			case yaml_error_type_t.YAML_PARSER_ERROR:
				return "Cannot parse the input stream.";
			case yaml_error_type_t.YAML_COMPOSER_ERROR:
				return "Cannot compose a YAML document.";
			case yaml_error_type_t.YAML_WRITER_ERROR:
				return "Cannot write to the output stream.";
			case yaml_error_type_t.YAML_EMITTER_ERROR:
				return "Cannot emit a YAML stream." ;
		}
		return errorCode.stringof ;
	}
	
	Element parseScalar (yaml_event_t* scalarEvent) {
		auto res = Node.Init(scalarEvent) ;
		if (res.anchor.length)
			aliases[res.anchor] = res;
		assert( res.type is Node.Scalar ) ;
		return res ;
	}
	
	
	Element parseSequence (yaml_event_t* seqEvent){
		auto res	=	Node.Init(seqEvent) ;
		assert( res.type is Node.Sequence );
		if (res.anchor.length)
			aliases[res.anchor] = res;
		yaml_event_t event;
		int parseRes;
		end_parsing:
		
		while (0 != (parseRes = yaml_parser_parse(&parser, &event))) {
			switch (event.type) {
				case yaml_event_type_t.YAML_ALIAS_EVENT:
					res.addNode( aliases[ fromStringz(cast(char*) event.data.alias_.anchor)] ) ;
					break;
				case yaml_event_type_t.YAML_SCALAR_EVENT:
					res.addNode( parseScalar(&event) );
					break;
				case yaml_event_type_t.YAML_SEQUENCE_START_EVENT:
					res.addNode( parseSequence(&event) );
					break;
				case yaml_event_type_t.YAML_MAPPING_START_EVENT:
					res.addNode(  parseMapping(&event) );
					break;
				case yaml_event_type_t.YAML_SEQUENCE_END_EVENT:
					break end_parsing;
				default:
					throw new Exception("invalid event type " ~ Int.toString(event.type) );
			}
		}
		if (!parseRes)
			throw new Exception("parse error: " ~ errorMsg );
		return res;
	}
	
	
	Element parseMapping (yaml_event_t* mapEvent) {
		auto res	= Node.Init(mapEvent);
		assert( res.type is Node.Mapping );
		if (res.anchor.length)
			aliases[res.anchor] = res;
		yaml_event_t	event;
		Element		key;
		int			parseRes;
		end_parsing:
		while (0 != (parseRes = yaml_parser_parse(&parser, &event))) {
			switch (event.type) {
				case yaml_event_type_t.YAML_ALIAS_EVENT:
					if (key is null)
						Error("parse error", __FILE__, __LINE__);
					else	{
						res.addNode( aliases[ fromStringz(cast(char*) event.data.alias_.anchor) ], key ) ;
						key		= null;
					}
					break;
				case yaml_event_type_t.YAML_SCALAR_EVENT:
					if (key is null)
						key		= parseScalar(&event);
					else	{
						res.addNode( parseScalar(&event), key ) ;
						key		= null;
					}
					break;
				case yaml_event_type_t.YAML_SEQUENCE_START_EVENT:
					if (key is null)
						Error("parse error", __FILE__, __LINE__);
					else	{
						res.addNode( parseSequence(&event), key ) ;
						key		= null;
					}
					break;
				case yaml_event_type_t.YAML_MAPPING_START_EVENT:
					if (key is null)
						Error("parse error", __FILE__, __LINE__);
					else {
						res.addNode( parseMapping(&event), key ) ;
						key		= null;
					}
					break;
				case yaml_event_type_t.YAML_MAPPING_END_EVENT:
					if (key !is null){
						res.addNode( null , key ) ;
						key 		= null;
					}
					break end_parsing;
				default:
					Error("parse error", __FILE__, __LINE__);
			}
		}
		if (!parseRes)
			Error("parse error: " ~ errorMsg, __FILE__, __LINE__);
		return res;
	}
	
	Doc* parse (char[] s) {
		auto sz	= toStringz(s) ;
		auto len	= strlenz(sz) ;
		yaml_parser_set_input_string(&parser, cast(ubyte*)sz,  len);
		
		yaml_event_t checkEvent (yaml_event_type_t type)	{
			yaml_event_t event;
			if (!yaml_parser_parse(&parser, &event))
				Error("parse error", __FILE__, __LINE__);
			if (event.type != type)
				Error("expected event " ~ Int.toString(event.type) ~ " given " ~  Int.toString(type), __FILE__, __LINE__);
			return event;
		}
		
		checkEvent(yaml_event_type_t.YAML_STREAM_START_EVENT);
		checkEvent(yaml_event_type_t.YAML_DOCUMENT_START_EVENT);
		auto mapEvent = checkEvent(yaml_event_type_t.YAML_MAPPING_START_EVENT);
		auto doc = Doc.Init(parseMapping(&mapEvent));
		checkEvent(yaml_event_type_t.YAML_DOCUMENT_END_EVENT);
		checkEvent(yaml_event_type_t.YAML_STREAM_END_EVENT);
		return doc;
	}
	
	void Error(char[] msg, char[] file, int line){
		throw new Exception(msg);
	}
}


version(yaml_test){
	import tango.io.Stdout , tango.io.device.File ;
	void main(){
		auto parser	= new Parser;
		auto da		= cast(char[]) File.get(`test.yaml`);
		auto doc		= parser.parse(da) ;
		auto n	= doc.root[doc.root[0]];
		Stdout.formatln("{}",  doc.root[0].value  );
		
		Stdout.formatln("{}", yaml_event_t.sizeof);
		
	}
}